#include "fileoperation.h"
#include <QDebug>
#include <QException>

FileOperation::FileOperation() {

}

FileOperation::FileOperation(QStringList srcPathList, QString desPath) {
  this->srcPathList = srcPathList;
  this->desPath = desPath;
}

FileOperation::FileOperation(QString srcPath, QString desPath) {
  this->srcPath = srcPath;
  this->desPath = desPath + "/";
}

bool FileOperation::isEncryptFile(QString srcPath) {
    QRegExp regx("(\\.ecr)$");
    if (srcPath.indexOf(regx) != -1) {
        QFile file(srcPath);
        file.open(QIODevice::ReadOnly);
        int length = 0;
        char* buffer = new char[3];
        if ((length = file.read(buffer, 3)) != 0) {
            if (QByteArray(buffer, length).toHex() == "454352") {
                return true;
            }
        }
    }
    return false;
}

bool FileOperation::validatePassword(QString password, QByteArray hashCode) {
  if (this->handleHash(password.toUtf8()).toHex() == hashCode.toHex()) {
    return true;
  } else {
    qDebug() << "wrong password";
    return false;
  }
}

int FileOperation::handleFileEncryption(int algorithm, int mode, QString password) {
  this->analyzeFile();
  int progressTotal = this->filePathList.size() + this->emptyFolder.size();
  QFile writeFile(this->desPath);
  QFile readFile;
  char* buffer;
  int length = 0;
  try {
    writeFile.open(QIODevice::ReadWrite | QIODevice::Append);
    writeFile.write(QByteArray::fromHex("454352"));
    // 写入文件开始标志
    writeFile.write(QByteArray::fromHex("454e4653"));
    // 处理密钥映射为64字节的hash值
    writeFile.write(this->handleHash(password.toUtf8()));
    // 加密算法相关
    writeFile.write(this->int2Byte(algorithm));
    writeFile.write(this->int2Byte(mode));

    double progress = 0;
    QDir relPath = this->srcPathList[0].left(this->srcPathList[0].lastIndexOf('/'));
    // empty folders
    for (int i = 0; i < this->emptyFolder.size(); i++) {
      writeFile.write(QByteArray::fromHex("4453ffff")); // 文件开始标志
      writeFile.write((relPath.relativeFilePath(this->emptyFolder[i]) + '/').toUtf8(), 2048); // 文件相对路径
      writeFile.write(QByteArray::fromHex("4445ffff")); // 文件结束标志
      progress += (1.0 / progressTotal);
      emit sendPro(progress);
    }
    for (int i = 0; i < this->filePathList.size(); i++) {
      QFileInfo fileInfo(this->filePathList[i]);
      if (fileInfo.isFile()) {
        readFile.setFileName(this->filePathList[i]);
        readFile.open(QIODevice::ReadOnly);
        // 加密content
        writeFile.write(QByteArray::fromHex("4453ffff")); // 文件开始标志
        writeFile.write(relPath.relativeFilePath(this->filePathList[i]).toUtf8(), 2048); // 文件相对路径
        double haveLen = 0;
        double totalLen = readFile.size();
        int size = readFile.size();
        if (size == 0) {
          progress += (1.0 / progressTotal);
          emit sendPro(progress);
          writeFile.write(QByteArray::fromHex("4445ffff")); // 文件结束标志
          readFile.close();
          continue;
        }
        buffer = new char[size];
        while ((length = readFile.read(buffer, size)) != 0) {
          haveLen = length;
          progress += ((haveLen / totalLen) / progressTotal);
          emit sendPro(progress);
          // 调用加密算法
          CCipher *cipher = cipherFactory.buildCipher(algorithm);
          QByteArray cipherRes;
          QByteArray plain = QByteArray(buffer, length);
          cipher->encrypt(plain, cipherRes, password.toUtf8(), true, mode);
          writeFile.write(cipherRes);
        }
        // 加密文件
        writeFile.write(QByteArray::fromHex("4445ffff")); // 文件结束标志
        readFile.close();
      } else {
        QFile::remove(desPath); //写文件异常则删除文件
        return 2;
      }
    }
    writeFile.write(QByteArray::fromHex("454e4645")); // 密文文件结束标志
    writeFile.seek(0);
    QByteArray hash;
    int size  = writeFile.size();
    buffer = new char[size];
    while ((length = writeFile.read(buffer, size)) != 0) {
      // 调用加密算法
      hash = this->handleHash(QByteArray(buffer, length));
    }
    writeFile.write(hash); //文件整体hash值
    writeFile.close();
  } catch (QException err) {
    writeFile.close();
    QFile::remove(desPath); //写文件异常则删除文件
    readFile.close();
    return 2;
  }
  return 0;
}

int FileOperation::handleFileDecryption(QString password) {
  if (!this->isEncryptFile(this->srcPath)) {
    qDebug() << "file type error";
    return 1;
  }
  QStringList fileList;
  QStringList dirList;
  char *buf;
  int length = 0;
  char* buffer;
  QFile writeFile;
  QFile readFile(this->srcPath);
  if (!readFile.exists()) {
    qDebug() << "valid file";
    return 4;
  }
  readFile.open(QIODevice::ReadOnly);
  int size = readFile.size() - 64;
  buf = new char[size];
  QByteArray hash;
  if ((length = readFile.read(buf, size)) != 0) {
    hash = this->handleHash(QByteArray(buf, length));
  }
  readFile.seek(readFile.size() - 64);
  buf = new char[64];
  QByteArray fileHash;
  if ((length = readFile.read(buf, 64)) != 0) {
    fileHash = QByteArray(buf, length);
  }
  if (fileHash.toHex() != hash.toHex()) {
    qDebug() << "文件被破坏";
    return 3;
  }

  int algorithm = 0, mode = 0;
  readFile.seek(3);
  try {
    bool fileContent = false, fileStart = false;
    buffer = new char[4];
    if ((length = readFile.read(buffer, 4)) != 0) {
      QString cipherStart = QByteArray(buffer, length).toHex();
      if (cipherStart != "454e4653") {
        this->deleteAllFiles(fileList);
        this->deleteAllDirs(dirList);
        return 3; //文件结构错误
      } else {
        fileStart = true;
      }
    }
    // 判断密码是否正确
    buffer = new char[64];
    if ((length = readFile.read(buffer, 64)) != 0) {
      if (!validatePassword(password, QByteArray(buffer, length))) {
        return 2;
      }
    }
    buffer = new char[1];
    if ((length = readFile.read(buffer, 1)) != 0) {
      algorithm = this->byte2Int(QByteArray(buffer, length));
    }
    buffer = new char[1];
    if ((length = readFile.read(buffer, 1)) != 0) {
      mode = this->byte2Int(QByteArray(buffer, length));
    }
    // 文件开始
    buffer = new char[4];
    QString relPath = "";
    double totalLen = readFile.size();
    double haveLen = 138;
    double progress = 0;
    progress += haveLen/totalLen;
    emit sendPro(progress);
    while(fileStart) {
      QByteArray readCipher;
      while ((length = readFile.read(buffer, 4)) != 0) {
        haveLen = length;
        progress += haveLen / totalLen;
        emit sendPro(progress);
        if ((QByteArray(buffer, length)).toHex() == "454e4645") {
          fileStart = false;
          break;
        }
        if ((QByteArray(buffer, length)).toHex() == "4453ffff") {
          if (fileContent) {
            this->deleteAllFiles(fileList);
            this->deleteAllDirs(dirList);
            return 3;
          }
          fileContent = true;
          char* buffer2 = new char[2048];
          if ((length = readFile.read(buffer2, 2048)) != 0) {
            haveLen = 2048;
            progress += haveLen / totalLen;
            emit sendPro(progress);
            relPath = QString(QByteArray(buffer2, length));
            if (relPath.endsWith('/')) {
              dirList << relPath.split('/')[0];
              this->createDir(this->desPath + relPath);
              continue;
            }
            fileList << (this->desPath + relPath);
            dirList << relPath.split('/')[0];
            writeFile.setFileName(this->desPath + relPath);
            this->createDir(this->desPath + relPath);
            writeFile.open(QIODevice::WriteOnly | QIODevice::Append);
          }
        } else if ((QByteArray(buffer, length)).toHex() == "4445ffff") {
          fileContent = false;
          if (writeFile.isOpen()) {
              CCipher *cipher = cipherFactory.buildCipher(algorithm);
              QByteArray plainRes;
              cipher->encrypt(readCipher, plainRes, password.toUtf8(), false, mode);
              writeFile.write(plainRes);
          }
          break;
        } else {
          readCipher.append(QByteArray(buffer, length));
        }
      }
      if (fileContent == true) {
        qDebug() << "file error!";
        this->deleteAllFiles(fileList);
        this->deleteAllDirs(dirList);
        return 3;
      }
      writeFile.close();

      if (!fileStart) {
        break;
      }
    }
    if (fileStart) {
      this->deleteAllFiles(fileList);
      this->deleteAllDirs(dirList);
      return 3;
    }
  } catch (QException err) {
    readFile.close();
    writeFile.close();
    this->deleteAllFiles(fileList);
    this->deleteAllDirs(dirList);
    return 4;
  }
  return 0;
}

void FileOperation::analyzeFile() {
  for (int i = 0; i < this->srcPathList.size(); i++) {
    QFileInfo fileInfo(this->srcPathList[i]);
    if (fileInfo.isDir()) {
      this->handleDir(this->srcPathList[i]);
    } else if (fileInfo.isFile()) {
      this->filePathList.append(this->srcPathList[i]);
    }
  }
}

void FileOperation::handleDir(QString path) {
  QDir dir(path);
  dir.setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
  dir.setSorting(QDir::DirsFirst);
  QFileInfoList list = dir.entryInfoList();
  if (list.size() < 1) {
    this->emptyFolder.append(path);
    return;
  }
  int i = 0;
  do {
    QFileInfo fileInfo = list.at(i);
    bool bisDir = fileInfo.isDir();
    if(bisDir)
      handleDir(fileInfo.filePath());
    else
      this->filePathList.append(fileInfo.filePath());
    ++i;
  } while(i<list.size());
}

void FileOperation::createDir(QString path) {
  QString folderPath = path.left(path.lastIndexOf('/'));
  QDir qDir(folderPath);
  qDebug() << qDir.mkpath(folderPath);
}

void FileOperation::deleteAllDirs(QStringList dirList) {
  for (int i = 0; i < dirList.size(); i++) {
    QDir dir(dirList[i]);
    if (dir.exists()) {
      dir.removeRecursively();
    }
  }
}

void FileOperation::deleteAllFiles(QStringList fileList) {
  for (int i = 0; i < fileList.size(); i++) {
    QFile::remove(fileList[i]);
  }
}

QByteArray FileOperation::int2Byte(int num) {
  QByteArray res;
  res.resize(1);
  res[0] = (0xff & num);
  return res;
}

int FileOperation::byte2Int(QByteArray bytes) {
  return bytes[0] & 0xff;
}

QByteArray FileOperation::handleHash(QByteArray data) {
  CCipher *hash = cipherFactory.buildCipher(3);
  QByteArray res;
  hash->encrypt(data, res, 4);
  return res;
}
